import constants from '@/constants';
import { BaseService } from '@/core/base.service';
import { SandService } from '@/core/sand.service';
import { UopService } from '@/core/uop.service';
import { WalletRechangeDto, WalletWithdrawDto } from '@/dtos';
import { BillStatusEnum, BillTypeEnum, UopPaymentTypeEnum } from '@/enums';
import { compareHash, halt } from '@/utils';
import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { Queue } from 'bull';
import dayjs from 'dayjs';
import { SettingService } from './setting.service';
import Decimal from 'decimal.js';
import { NbcbService } from '@/core/nbcb.service';

@Injectable()
export class WalletService extends BaseService {
  constructor(
    @InjectQueue('bill')
    private readonly billQueue: Queue,
    private readonly settingService: SettingService,
    private readonly uopService: UopService,
    private readonly sandService: SandService,
    private readonly nbcbService: NbcbService,
  ) {
    super();
  }

  async recharge({ type, amount, ip }: WalletRechangeDto, userId: number) {
    return await this.redlock.using(
      [`recharge:${userId}`],
      10000,
      {},
      async () => {
        const user = await this.prisma.user.findUnique({
          where: { id: userId },
        });
        if (!user.isReal) {
          return halt('请先完成实名认证');
        }
        if (
          (await this.prisma.bill.count({
            where: {
              type: BillTypeEnum.RECHARGE,
              status: BillStatusEnum.PENDING,
              ownerId: userId,
            },
          })) > 0
        ) {
          return halt('请先完成上一笔充值订单');
        }
        const currentTime = dayjs();
        const expireTime = currentTime.add(3, 'minutes');
        let payment = null;
        if (type === UopPaymentTypeEnum.SAND) {
          payment = await this.sandService.recharge({
            orderNo: await this.sandService.generateOrderNo(),
            orderAmt: amount.toString(),
            ip,
            notifyUrl: `${this.sandService.NotifyUrl}/api/wallet/sand/callback`,
            returnUrl: this.sandService.ReturnUrl,
            goodsName: `${this.config.get('name')}充值: ¥${amount}`,
            extra: JSON.stringify({}),
          });
          console.log('杉徳充值下单: ', payment);
        } else {
          payment = await this.uopService.order({
            type,
            totalAmount: amount * 100,
            expireTime: expireTime.format('YYYY-MM-DD HH:mm:ss'),
            notifyUrl: `${this.uopService.NotifyUrl}/api/wallet/uop/callback`,
            returnUrl: this.uopService.ReturnUrl,
            orderDesc: `${this.config.get('name')}充值: ¥${amount}`,
            // goods: JSON.stringify([{ body: `${this.config.get('name')}充值: ¥${amount}` }]),
          });
          console.log('银商充值下单: ', payment);
        }
        if (!payment || !payment?.url) {
          return halt('充值订单信息异常');
        }
        const order = await this.prisma.bill.create({
          data: {
            type: BillTypeEnum.RECHARGE,
            total: Number(amount),
            balance: user.balance,
            frozen: user.frozen,
            ownerId: userId,
            no: payment.no,
            payType: type,
            payUrl: payment.url,
            expireAt: expireTime.toDate(),
            createdAt: currentTime.toDate(),
            date: new Date(),
          },
        });
        await this.billQueue.add(
          'cancel',
          {
            no: order.no,
          },
          {
            delay: expireTime.diff(dayjs()),
          },
        );
        return payment;
      },
    );
  }

  async withdraw({ pass, bankId, amount }: WalletWithdrawDto, userId: number) {
    return await this.redlock.using(
      [`withdraw:${userId}`],
      10000,
      {},
      async () => {
        const bank = await this.prisma.bank.findUnique({
          where: {
            id: bankId,
            ownerId: userId,
          },
        });
        if (!bank) {
          return halt('银行卡信息异常');
        }

        if (
          (await this.prisma.bill.count({
            where: {
              type: BillTypeEnum.WITHDRAW,
              status: {
                in: [
                  BillStatusEnum.PENDING,
                  BillStatusEnum.PROCESSING,
                  BillStatusEnum.COMPLETED,
                ],
              },
              ownerId: userId,
              createdAt: {
                gte: dayjs().startOf('day').toDate(),
                lte: dayjs().endOf('day').toDate(),
              },
            },
          })) > 0
        ) {
          return halt('每日只能提现一次');
        }

        const setting = await this.settingService.getDefault();
        if (!setting) {
          return halt('系统配置异常');
        }

        return await this.prisma.$transaction(async (tx) => {
          const user = await tx.user.findUnique({ where: { id: userId } });
          if (!user.isReal) {
            return halt('请先完成实名认证');
          }
          if (!user.txPass) {
            return halt('请先设置操作密码');
          }
          if (!(await compareHash(pass, user.txPass))) {
            return halt('密码错误');
          }
          if (Number(amount) > Number(user.balance)) {
            return halt('余额不足');
          }
          await tx.user.update({
            where: {
              id: user.id,
              version: user.version,
            },
            data: {
              version: {
                increment: 1,
              },
              balance: {
                decrement: Number(amount),
              },
              frozen: {
                increment: Number(amount),
              },
            },
          });
          await tx.bill.create({
            data: {
              type: BillTypeEnum.WITHDRAW,
              total: Number(amount),
              fee: new Decimal(Number(amount) * Number(setting.withdrawFee))
                .toDecimalPlaces(2)
                .toNumber(),
              balance: user.balance,
              frozen: user.frozen,
              ownerId: userId,
              no: await this.uopService.generateOrderNo(),
              serialNo: await this.nbcbService.generateSerialNo(),
              extra: bank,
            },
          });
          return true;
        });
      },
    );
  }
}
